/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ServerPackage;

import ClientPackage.ClientInfo;
import SecureUtil.DiffieHellman;
import SecureUtil.PBEHelper;
import SecureUtil.Packer;
import SecureUtil.RSAHelper;
import SecureUtil.UDPHelper;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import javax.crypto.SecretKey;

/**
 *
 * @author Gwang
 */
public class LoginAssistant implements Runnable{
    private UDPHelper udpagent;
    private byte[] initMessage;
    private byte[] N1;
    private CenterServer server;
    private ClientInfo client;
    public LoginAssistant(CenterServer server, InetAddress ip, int port, byte[] initMessage) throws Exception{
        client = new ClientInfo(ip,port,"");
        udpagent = new UDPHelper(new DatagramSocket());
        this.initMessage = initMessage;
        this.server = server;
    }
    @Override
    public void run() {
        boolean success = Message34();   
        if(!success) return;
        System.out.println("successfully received message34");
       
        success = Message56();       
        if(!success) return;
        System.out.println("successfully received message56");
        server.loginUser(client);
        System.out.println("login successfully: "+client._username);
        
        //System.out.println(client._ip + ":" + client._port);
        //System.out.println("client public key: " + new String(client._pubkey.getEncoded()));
        //System.out.println("client session key: " + new String(client._sessionkey.getEncoded()));
        
        
        
    }
    /**
     * A->S:    LOGIN, Cookie, {A,  ga mod p}server
     * A<-S:    salt, W{N1, gb mod p}; W=Hash(passwd|salt);
     * @return true pass, false some error occur;
     */
    private boolean Message34(){
        Packer packer = new Packer();
        RSAHelper rsa = new RSAHelper();
        DiffieHellman DHserver = new DiffieHellman();
        try{
                    rsa.privKey = server._serverprivKey;
                    
                    //TODO byte[] ADH1 = rsa.RSADecrypt(initMessage);
                    
                    byte[] ADH1 = initMessage;
                    
                    packer.unpack(ADH1);
                    byte [] uNameBytes =  rsa.RSADecrypt( (byte []) packer.getNext(Packer.DataType.BYTES) );
                    
                    client._username = new String(uNameBytes);
                    Credentials credential = server.searchRegistedUserByName(client._username);
                    
                    if(credential == null){
                        System.err.println("login failed: user " +
                                client._username+" not registed");
                        return false;
                    }
                    System.out.println("User found: "+ credential._userName +" "+credential._salt );

                    // prepare Message 4
                    String salt = credential._salt;
                    byte[] DH1 = (byte[]) packer.getNext(Packer.DataType.BYTES);
                    byte[] serverPubKey = DHserver.processFirstHalf(DH1);
                    client._sessionkey = DHserver.serverSharedKey();
                    SecureRandom rand = new SecureRandom();
                    N1 = new byte[24];
                    rand.nextBytes(N1);
                    packer.Reset();
                    packer.addElement(Packer.DataType.BYTES, N1);
                    packer.addElement(Packer.DataType.BYTES, serverPubKey);
                    SecretKey pbeKey = PBEHelper.getPBEKey(credential._passwordHash, credential._salt);
                    byte[] WN1DH2 = PBEHelper.PBEencrypt(pbeKey, packer.pack());
                    // assemble Message 4:
                    // salt, W{N1, gb mod p}; W=Hash(passwd|salt);
                    packer.Reset();
                    packer.addElement(Packer.DataType.STRING, salt);
                    packer.addElement(Packer.DataType.BYTES, WN1DH2);
                    byte[] message4 = packer.pack();
                    
                    this.udpagent.sendBytes(message4, client._ip, client._port);
                    return true;
        }catch(Exception e){
            System.err.println("illegal login message34 in server");
            e.printStackTrace();
            return false;
        }
    }
    /**
     * msg5: A->S:    Ka{A, N1, KA, N2} ; Ka = gab mod p
     * msg6: A<-S:    Ka{N2}
     * 
     * @return true for success, otherwise false;
     */
    private boolean Message56(){
        try{
        byte[] msg5 = this.udpagent.receiveBytes(20000);
        DiffieHellman dhworker = new DiffieHellman();
        byte[] AN1KAN2 = dhworker.decryptDH(msg5,client._sessionkey);
        Packer packer = new Packer();
        packer.unpack(AN1KAN2);
        // username avoid attack
        String username = (String) packer.getNext(Packer.DataType.STRING);
        if(!username.equals(client._username)) {
            System.err.println("warning username does not match between msg34 and msg56!");
            return false;
        }
        // N1 for authenticating A
        byte[] N1back = (byte[]) packer.getNext(Packer.DataType.BYTES);
        if(!Arrays.equals(N1, N1back)){
            System.err.println(username+" not get authenticated (can not back with N1)!");
            return false;
        }
        // KA: A's PubKey
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        byte[] KA = (byte[]) packer.getNext(Packer.DataType.BYTES);
        EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(KA);
        client._pubkey = keyFactory.generatePublic(publicKeySpec);

        // N2
        byte[] N2 = (byte[]) packer.getNext(Packer.DataType.BYTES);

        // send msg6;
        byte[] msg6 = dhworker.encryptDH(N2, client._sessionkey);
        this.udpagent.sendBytes(msg6, client._ip, client._port);
        return true;
        }catch (Exception e){
            System.err.println("login failed in Message 56 in server");
            e.printStackTrace();
            return false;
        }
    }
}
